/*
 * PDLogger
 * 
 * Copyright (c) 2012, Zoujie Wu <yibn2008@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 *   * Redistributions of source code must retain the above copyright notice, this 
 *     list of conditions and the following disclaimer.
 *   
 *   * Redistributions in binary form must reproduce the above copyright notice, 
 *     this list of conditions and the following disclaimer in the documentation 
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* $Id$ */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "ext/standard/php_string.h"
#include "ext/standard/php_smart_str.h"
#include "php_pdlogger.h"
#include "pdl_record.h"
#include "pdl_watch.h"
#include "pdl_override.h"

#include "ext/pdo/php_pdo_driver.h"

PDL_API int pdl_get_override_func(HashTable *ht, char *key, uint key_len, pdl_override_func_t **ofunc TSRMLS_DC)
{
	char	*lower_key;

	lower_key = zend_str_tolower_dup(key, key_len);

	if (zend_hash_find(ht, lower_key, key_len + 1, (void **) ofunc) == FAILURE) {
		php_error_docref(NULL TSRMLS_CC, E_ERROR, "Unable to get override info of internal function/method %s", key);
		return FAILURE;
	}

	efree(lower_key);

	return SUCCESS;
}

/**
 * add override functions
 */
PDL_API int pdl_add_override_func(HashTable *ht, char *class_name, int class_len, char *func_name, int func_len, php_func override TSRMLS_DC)
{
	zend_class_entry	**pce;
	zend_function		*func;
	pdl_override_func_t ofunc;
	char				*key;
	char				*lower_class = NULL, *lower_func;
	int					key_len = func_len + (class_name ? (class_len + 2) : 0) + 1;
	zend_bool			ret = FAILURE;

	if (class_name) {
		lower_class = zend_str_tolower_dup(class_name, class_len);
	}
	lower_func = zend_str_tolower_dup(func_name, func_len);

	key = (char *) emalloc(sizeof(char) * key_len);
	snprintf(key, key_len, "%s%s%s", lower_class ? lower_class : "", lower_class ? "::" : "", lower_func);
	key[key_len - 1] = 0;

	if (!zend_hash_exists(ht, key, key_len)) {
		if (class_name) {
			if ((zend_hash_find(CG(class_table), lower_class, class_len + 1, (void **) &pce) == SUCCESS) && ((*pce)->type == ZEND_INTERNAL_CLASS)) {
				if (zend_hash_find(&(*pce)->function_table, lower_func, func_len + 1, (void **) &func) == SUCCESS) {
					ofunc.ce = *pce;
					ofunc.override = override;
					ofunc.origin = func->internal_function.handler;
					ofunc.func = func;

					ret = zend_hash_add(ht, key, key_len, &ofunc, sizeof(pdl_override_func_t), NULL);
				}
			}
		} else {
			if ((zend_hash_find(CG(function_table), lower_func, func_len + 1, (void **) &func) == SUCCESS) && (func->type == ZEND_INTERNAL_FUNCTION)) {
				ofunc.ce = NULL;
				ofunc.override = override;
				ofunc.origin = func->internal_function.handler;
				ofunc.func = func;

				ret = zend_hash_add(ht, key, key_len, &ofunc, sizeof(pdl_override_func_t), NULL);
			}
		}
	}

	if (lower_class) {
		efree(lower_class);
	}
	efree(lower_func);

	return ret;
}

/* override/recover internal functions */
PDL_API void pdl_replace_override_funcs(int override TSRMLS_DC)
{
	HashTable *ht = &PDL_G(override_funcs);

	for (zend_hash_internal_pointer_reset(ht); zend_hash_has_more_elements(ht) == SUCCESS; zend_hash_move_forward(ht)) {
		pdl_override_func_t *ofunc;

        if (zend_hash_get_current_data(ht, (void**) &ofunc) == SUCCESS) {
			ofunc->func->internal_function.handler = override ? ofunc->override : ofunc->origin;
        }
	}
}

#define PDO_STATUS(is_ok) (is_ok ? "SUCCESS" : "FAILURE")

#define PDO_DBH_ERROR_DATA(data) \
	do { \
		dbh = (pdo_dbh_t *) zend_object_store_get_object(this_ptr TSRMLS_CC); \
		if (EG(exception) || (dbh->error_mode == PDO_ERRMODE_EXCEPTION)) { \
			level = PDL_LV_EXCEPTION; \
			data = EG(exception); \
		} else if (dbh->error_mode == PDO_ERRMODE_SILENT) { \
			level = PDL_LV_WARNING; \
			tmp_len = pdl_pdo_error_str(&tmp_str, dbh, NULL TSRMLS_CC); \
			MAKE_STD_ZVAL(data); \
			ZVAL_STRINGL(data, tmp_str, tmp_len, 0); \
		} \
	} while (0)

#define PDO_RECORD_BEGIN(class_name, method) \
	do { \
		t = pdl_get_curr_time(); \
		PDL_GET_O_METHOD(class_name, method, ofunc); \
		ofunc->origin(INTERNAL_FUNCTION_PARAM_PASSTHRU); \
		if (!PDL_G(enable) || !(PDL_G(watch) & PDL_W_PDO)) { \
			return; \
		} \
	} while (0)

#define PDO_RECORD_END(label_str, desc_str) \
	do { \
		msec = pdl_calc_interval(t) * 1000; \
		msg_len = spprintf(&msg, 0, "%s in %.3f msec(s), %s", desc_str, msec, PDO_STATUS(is_ok)); \
		pdl_record(level, label_str, sizeof(label_str)-1, msg, msg_len, NULL, 0, data TSRMLS_CC); \
		if (data && (Z_TYPE_P(data) == IS_STRING)) { \
			zval_ptr_dtor(&data); \
		} \
		efree(msg); \
	} while (0)

/**
 * get pdo error as string
 */
static int pdl_pdo_error_str(char **str, pdo_dbh_t *dbh, pdo_stmt_t *stmt TSRMLS_DC)
{
	zval		*error_info = NULL;
	zval		**perror_msg = NULL;
	pdo_dbh_t	*tmp_dbh;
	int			str_len = 0;

	tmp_dbh = stmt ? stmt->dbh : dbh;

	if (tmp_dbh && tmp_dbh->methods->fetch_err) {
		MAKE_STD_ZVAL(error_info);
		array_init(error_info);

		tmp_dbh->methods->fetch_err(tmp_dbh, stmt, error_info TSRMLS_CC);
		zend_hash_index_find(Z_ARRVAL_P(error_info), 1, (void **) &perror_msg);

		str_len = spprintf(str, 0, "SQLSTATE[%s]: %s", tmp_dbh->error_code, perror_msg ? Z_STRVAL_PP(perror_msg) : "Unknown error");

		zval_ptr_dtor(&error_info);
	}

	return str_len;
}

/* {{{ proto PDO::__construct(strin $dsn[, string $username[, string $password[, array $options]]])
 */
PDL_O_METHOD(PDO, __construct)
{
	pdl_override_func_t *ofunc;
	pdo_dbh_t			*dbh;
	pdl_time_t			t;
	double				msec;
	char				*msg;
	uint				msg_len;
	char				*tmp_str;
	uint				tmp_len;
	const char			*tpl;
	zval				*data = NULL;
	int					level = PDL_LV_INFO;
	zend_bool			is_ok = 1;

	PDO_RECORD_BEGIN(PDO, __construct);

	/* constructor has no return value */
	if (!this_ptr || (Z_TYPE_P(this_ptr) == IS_NULL)) {
		data = EG(exception);
		level = PDL_LV_EXCEPTION;
		is_ok = 0;
	} else {
		dbh = (pdo_dbh_t *) zend_object_store_get_object(this_ptr TSRMLS_CC);
		if (dbh) {
			tpl = "Connect database with %s driver\n"
				  "- data source: %s\n"
				  "- username: %s\n"
				  "- persistent: %s, id = %s\n"
				  "- auto commit: %s";
			tmp_len = spprintf(&tmp_str, 0, tpl, 
				dbh->driver->driver_name,
				dbh->data_source,
				dbh->username,
				dbh->is_persistent ? "true" : "false",
				dbh->persistent_id,
				dbh->auto_commit ? "true" : "false");

			MAKE_STD_ZVAL(data);
			ZVAL_STRINGL(data, tmp_str, tmp_len, 0);
		}
	}

	PDO_RECORD_END("PDO", "Create PDO object");
}
/* }}} */

/* {{{ proto PDOStatement PDO::prepare(string $statment[, array $driver_options = array()])
 */
PDL_O_METHOD(PDO, prepare)
{
	pdl_override_func_t *ofunc;
	pdl_time_t			t;
	pdo_stmt_t			*stmt;
	pdo_dbh_t			*dbh;
	double				msec;
	char				*msg;
	uint				msg_len;
	char				*tmp_str;
	uint				tmp_len;
	zval				*data = NULL;
	int					level = PDL_LV_INFO;
	zend_bool			is_ok = 1;

	PDO_RECORD_BEGIN(PDO, prepare);

	if (!Z_BVAL_P(return_value)) {
		is_ok = 0;
		PDO_DBH_ERROR_DATA(data);
	} else {
		stmt = (pdo_stmt_t* ) zend_object_store_get_object(return_value TSRMLS_CC);
		tmp_len = spprintf(&tmp_str, 5 + stmt->query_stringlen, "SQL: %s", stmt->query_string);

		MAKE_STD_ZVAL(data);
		ZVAL_STRINGL(data, tmp_str, tmp_len, 0);
	}

	PDO_RECORD_END("PDO", "Prepare SQL statement");
}
/* }}} */

/* {{{ proto object PDO::query(string $statement[, PDOStatement::setFetchMode() args]
 */
PDL_O_METHOD(PDO, query)
{
	pdl_override_func_t *ofunc;
	pdl_time_t			t;
	pdo_stmt_t			*stmt;
	pdo_dbh_t			*dbh;
	double				msec;
	char				*msg;
	uint				msg_len;
	char				*tmp_str;
	uint				tmp_len;
	zval				*data = NULL;
	int					level = PDL_LV_INFO;
	zend_bool			is_ok = 1;

	PDO_RECORD_BEGIN(PDO, query);

	if ((Z_TYPE_P(return_value) == IS_BOOL) && !Z_BVAL_P(return_value)) {
		is_ok = 0;
		PDO_DBH_ERROR_DATA(data);
	} else {
		stmt = (pdo_stmt_t* ) zend_object_store_get_object(return_value TSRMLS_CC);
		tmp_len = spprintf(&tmp_str, 5 + stmt->query_stringlen, "SQL: %s", stmt->query_string);

		MAKE_STD_ZVAL(data);
		ZVAL_STRINGL(data, tmp_str, tmp_len, 0);
	}

	PDO_RECORD_END("PDO", "Query SQL");
}
/* }}} */

/* {{{ proto int PDO::exec(string $statement)
 */
PDL_O_METHOD(PDO, exec)
{
	pdl_override_func_t *ofunc;
	pdl_time_t			t;
	pdo_dbh_t			*dbh;
	double				msec;
	char				*msg;
	uint				msg_len;
	char				*tmp_str;
	uint				tmp_len;
	char				*data_str;
	uint				data_len;
	zval				*data = NULL;
	int					level = PDL_LV_INFO;
	zend_bool			is_ok = 1;

	PDO_RECORD_BEGIN(PDO, exec);

	if ((Z_TYPE_P(return_value) == IS_BOOL) && !Z_BVAL_P(return_value)) {
		is_ok = 0;
		PDO_DBH_ERROR_DATA(data);
	} else if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &tmp_str, &tmp_len) == SUCCESS) {
		data_len = spprintf(&data_str, tmp_len + 5, "SQL: %s", tmp_str);

		MAKE_STD_ZVAL(data);
		ZVAL_STRINGL(data, data_str, data_len, 0);
	}

	PDO_RECORD_END("PDO", "Execute SQL");
}
/* }}} */

/* {{{ proto bool PDO::beginTransaction()
 */
PDL_O_METHOD(PDO, beginTransaction)
{
	pdl_override_func_t *ofunc;
	pdl_time_t			t;
	pdo_dbh_t			*dbh;
	double				msec;
	char				*msg;
	uint				msg_len;
	char				*tmp_str;
	uint				tmp_len;
	zval				*data = NULL;
	int					level = PDL_LV_INFO;
	zend_bool			is_ok = 1;

	PDO_RECORD_BEGIN(PDO, beginTransaction);

	if (!Z_BVAL_P(return_value)) {
		is_ok = 0;
		PDO_DBH_ERROR_DATA(data);
	}

	PDO_RECORD_END("PDO", "Begin transaction");
}
/* }}} */

/* {{{ proto bool PDO::rollBack()
 */
PDL_O_METHOD(PDO, rollBack)
{
	pdl_override_func_t *ofunc;
	pdl_time_t			t;
	pdo_dbh_t			*dbh;
	double				msec;
	char				*msg;
	uint				msg_len;
	char				*tmp_str;
	uint				tmp_len;
	zval 				*data = NULL;
	int					level = PDL_LV_INFO;
	zend_bool			is_ok = 1;

	PDO_RECORD_BEGIN(PDO, rollBack);

	if (!Z_BVAL_P(return_value)) {
		is_ok = 0;
		PDO_DBH_ERROR_DATA(data);
	}

	PDO_RECORD_END("PDO", "Roll back transaction");
}
/* }}} */

/* {{{ proto bool PDO::commit()
 */
PDL_O_METHOD(PDO, commit)
{
	pdl_override_func_t *ofunc;
	pdl_time_t			t;
	pdo_dbh_t			*dbh;
	double				msec;
	char				*msg;
	uint				msg_len;
	char				*tmp_str;
	uint				tmp_len;
	zval				*data = NULL;
	int					level = PDL_LV_INFO;
	zend_bool			is_ok = 1;

	PDO_RECORD_BEGIN(PDO, commit);

	if (!Z_BVAL_P(return_value)) {
		is_ok = 0;
		PDO_DBH_ERROR_DATA(data);
	}
	
	PDO_RECORD_END("PDO", "Commit transaction");
}
/* }}} */

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
